﻿using System.Linq.Dynamic.Core;
using System.Reflection;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 业务数据附属集合类数据处理服务
/// 不提供一次性新增或修改所有附属集合的功能, 因为不明确附属集合类型的数量, 实际业务中需要的时候调用即可
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
public class DataWithCollectionService<TDbContext, TEntitySet, TElement, TKey> :
    DataService<TDbContext, TEntitySet, TKey>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement, TKey>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<TKey>
    where TElement : class, IEntitySet<TKey>
    where TKey : IConvertible
{
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public DataWithCollectionService(
        TDbContext context,
        ILogger<DataWithCollectionService<TDbContext, TEntitySet, TElement, TKey>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting) { }

    #region 处理对象中的有引用关系的集合
    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">当前业务对象</param>
    /// <param name="collection">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task AddCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? collection = null, string? foreignKey = null)
    {
        collection ??= GetCollection(entitySet);
        if (entitySet.Id != null && (!entitySet.Id.Equals(default)) && collection != null && collection.Any())
        {
            var type = typeof(TElement);
            foreignKey ??= type.GetForeignKey<TEntitySet, TKey>();
            if (string.IsNullOrWhiteSpace(foreignKey))
            {
                throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
            }

            var propertyInfo = type.GetProperty(foreignKey);
            if (propertyInfo != null)
            {
                foreach (var entity in collection)
                {
                    if (entity != null)
                    {
                        Create(entity);
                        Update(entity);
                        propertyInfo.SetValue(entity, entitySet.Id);
                        await _context.AddAsync(entity);
                    }
                }
            }
        }
    }
    /// <summary>
    /// 更新业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">当前业务对象</param>
    /// <param name="collection">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task UpdateCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? collection = null, string? foreignKey = null)
    {
        collection ??= GetCollection(entitySet);
        if (entitySet.Id != null && (!entitySet.Id.Equals(default)) && collection != null && collection.Any())
        {
            var type = typeof(TElement);
            foreignKey ??= type.GetForeignKey<TEntitySet, TKey>();
            if (string.IsNullOrWhiteSpace(foreignKey))
            {
                throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
            }

            var propertyInfo = type.GetProperty(foreignKey);
            if (propertyInfo != null)
            {
                var news = collection ?? new List<TElement>();
                var olds = await GetQueryable<TEntitySet>().Where($"{foreignKey} == @0", entitySet.Id).ToListAsync();
                foreach (var entity in news)
                {
                    var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
                    if (old == null)
                    {
                        //新的有旧的没有, 则新增
                        Create(entity);
                        Update(entity);
                        propertyInfo.SetValue(entity, entitySet.Id);
                        await _context.AddAsync(entity);
                    }
                    else
                    {
                        //新的有旧的也有, 则更新
                        entity.Adapt(old, _adapterConfig);
                        Update(old);
                        _context.Update(old);
                    }
                }

                foreach (var old in olds)
                {
                    if (!news.Any(x => x.Id.Equals(old.Id)))
                    {
                        _context.Remove(old);
                    }
                }
            }
        }
    }
    /// <summary>
    /// 删除业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public virtual async Task DeleteCollectionAsync(TKey id, string? foreignKey = null)
    {
        foreignKey ??= typeof(TElement).GetForeignKey<TEntitySet, TKey>();
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
        }

        var elements = await GetQueryable<TElement>().Where($"{foreignKey} == @0", id).ToListAsync();
        if (elements != null && elements.Count > 0)
        {
            _context.RemoveRange(elements);
        }
    }
    #endregion

    #region 重载的增删改方法
    /// <summary>
    /// 重载的新增方法会同时将当前类型的附加信息一并新增
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public override async Task<TEntitySet> AddAsync(TEntitySet entitySet)
    {
        var autoSaveChanges = AutoSaveChanges;
        AutoSaveChanges = false;

        await base.AddAsync(entitySet);
        await AddCollectionAsync(entitySet, GetCollection(entitySet));

        AutoSaveChanges = autoSaveChanges;
        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }

        return entitySet;
    }
    /// <summary>
    /// 重载的更新方法会同时将当前类型的附加信息一并更新
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public override async Task<TEntitySet> UpdateAsync(TEntitySet entitySet)
    {
        var autoSaveChanges = AutoSaveChanges;
        AutoSaveChanges = false;

        entitySet = await base.UpdateAsync(entitySet);
        await UpdateCollectionAsync(entitySet, GetCollection(entitySet));

        AutoSaveChanges = autoSaveChanges;
        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }

        return entitySet;
    }
    /// <summary>
    /// 删除记录及其附属信息
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <returns></returns>
    public override async Task DeleteAsync(TKey id)
    {
        var autoSaveChanges = AutoSaveChanges;
        AutoSaveChanges = false;

        await DeleteCollectionAsync(id);
        await base.DeleteAsync(id);

        AutoSaveChanges = autoSaveChanges;
        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }
    }
    #endregion

    /// <summary>
    /// 获取类型的附加信息, 默认一个类型只有一个附加信息
    /// </summary>
    /// <param name="entitySet"></param>
    /// <returns></returns>
    protected virtual IEnumerable<TElement>? GetCollection(TEntitySet entitySet)
    {
        var propertyInfo = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.PropertyType.IsFromType<IEnumerable<TElement>>());
        if (propertyInfo != null && propertyInfo.GetValue(entitySet) is IEnumerable<TElement> additionals)
        {
            return additionals;
        }

        return null;
    }
}

/// <summary>
/// 业务数据附属集合类数据处理服务
/// 字符串作为键的业务类型默认实现
/// 不提供一次性新增或修改所有附属集合的功能, 因为不明确附属集合类型的数量, 实际业务中需要的时候调用即可
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
public class DataWithCollectionService<TDbContext, TEntitySet, TElement> :
    DataWithCollectionService<TDbContext, TEntitySet, TElement, string>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<string>
    where TElement : class, IEntitySet<string>
{
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public DataWithCollectionService(
        TDbContext context,
        ILogger<DataWithCollectionService<TDbContext, TEntitySet, TElement, string>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting) { }
}